"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

require("core-js/modules/es.function.name");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));

var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));

var _logger = _interopRequireDefault(require("./utils/logger.js"));

var _speedSampler = _interopRequireDefault(require("./speed-sampler.js"));

var _loader = require("./loader.js");

var _exception = require("./utils/exception.js");

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

// Universal IO Loader, implemented by adding Range header in xhr's request header
var RangeLoader = /*#__PURE__*/function (_BaseLoader) {
  (0, _inherits2.default)(RangeLoader, _BaseLoader);

  var _super = _createSuper(RangeLoader);

  (0, _createClass2.default)(RangeLoader, null, [{
    key: "isSupported",
    value: function isSupported() {
      try {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'https://example.com', true);
        xhr.responseType = 'arraybuffer';
        return xhr.responseType === 'arraybuffer';
      } catch (e) {
        _logger.default.w('RangeLoader', e.message);

        return false;
      }
    }
  }]);

  function RangeLoader(seekHandler, config) {
    var _this;

    (0, _classCallCheck2.default)(this, RangeLoader);
    _this = _super.call(this, 'xhr-range-loader');
    _this.TAG = 'RangeLoader';
    _this._seekHandler = seekHandler;
    _this._config = config;
    _this._needStash = false;
    _this._chunkSizeKBList = [128, 256, 384, 512 //768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192
    ];
    _this._currentChunkSizeKB = config.chunkSize || 384;
    _this._currentSpeedNormalized = 0;
    _this._zeroSpeedChunkCount = 0;
    _this._xhr = null;
    _this._speedSampler = new _speedSampler.default();
    _this._requestAbort = false;
    _this._waitForTotalLength = false;
    _this._totalLengthReceived = false;
    _this._currentRequestURL = null;
    _this._currentRedirectedURL = null;
    _this._currentRequestRange = null;
    _this._totalLength = null; // size of the entire file

    _this._contentLength = null; // Content-Length of entire request range

    _this._receivedLength = 0; // total received bytes

    _this._lastTimeLoaded = 0; // received bytes of current request sub-range

    return _this;
  }

  (0, _createClass2.default)(RangeLoader, [{
    key: "destroy",
    value: function destroy() {
      if (this.isWorking()) {
        this.abort();
      }

      if (this._xhr) {
        this._xhr.onreadystatechange = null;
        this._xhr.onprogress = null;
        this._xhr.onload = null;
        this._xhr.onerror = null;
        this._xhr = null;
      }

      (0, _get2.default)((0, _getPrototypeOf2.default)(RangeLoader.prototype), "destroy", this).call(this);
    }
  }, {
    key: "open",
    value: function open(dataSource, range) {
      this._dataSource = dataSource;
      this._range = range;
      this._status = _loader.LoaderStatus.kConnecting;
      var useRefTotalLength = false;

      if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) {
        useRefTotalLength = true;
        this._totalLength = this._dataSource.filesize;
      }

      if (!this._totalLengthReceived && !useRefTotalLength) {
        // We need total filesize
        this._waitForTotalLength = true;

        this._internalOpen(this._dataSource, {
          from: 0,
          to: -1
        });
      } else {
        // We have filesize, start loading
        this._openSubRange();
      }
    }
  }, {
    key: "_openSubRange",
    value: function _openSubRange() {
      var chunkSize = this._currentChunkSizeKB * 1024;
      var from = this._range.from + this._receivedLength;
      var to = from + chunkSize;

      if (this._contentLength != null) {
        if (to - this._range.from >= this._contentLength) {
          to = this._range.from + this._contentLength - 1;
        }
      }

      this._currentRequestRange = {
        from: from,
        to: to
      };

      this._internalOpen(this._dataSource, this._currentRequestRange);
    }
  }, {
    key: "_internalOpen",
    value: function _internalOpen(dataSource, range) {
      this._lastTimeLoaded = 0;
      var sourceURL = dataSource.url;

      if (this._config.reuseRedirectedURL) {
        if (this._currentRedirectedURL != undefined) {
          sourceURL = this._currentRedirectedURL;
        } else if (dataSource.redirectedURL != undefined) {
          sourceURL = dataSource.redirectedURL;
        }
      }

      var seekConfig = this._seekHandler.getConfig(sourceURL, range);

      this._currentRequestURL = seekConfig.url;
      var xhr = this._xhr = new XMLHttpRequest();
      xhr.open('GET', seekConfig.url, true);
      xhr.responseType = 'arraybuffer';
      xhr.onreadystatechange = this._onReadyStateChange.bind(this);
      xhr.onprogress = this._onProgress.bind(this);
      xhr.onload = this._onLoad.bind(this);
      xhr.onerror = this._onXhrError.bind(this);

      if (dataSource.withCredentials) {
        xhr.withCredentials = true;
      }

      if ((0, _typeof2.default)(seekConfig.headers) === 'object') {
        var headers = seekConfig.headers;

        for (var key in headers) {
          if (headers.hasOwnProperty(key)) {
            xhr.setRequestHeader(key, headers[key]);
          }
        }
      } // add additional headers


      if ((0, _typeof2.default)(this._config.headers) === 'object') {
        var _headers = this._config.headers;

        for (var _key in _headers) {
          if (_headers.hasOwnProperty(_key)) {
            xhr.setRequestHeader(_key, _headers[_key]);
          }
        }
      }

      xhr.send();
    }
  }, {
    key: "abort",
    value: function abort() {
      this._requestAbort = true;

      this._internalAbort();

      this._status = _loader.LoaderStatus.kComplete;
    }
  }, {
    key: "_internalAbort",
    value: function _internalAbort() {
      if (this._xhr) {
        this._xhr.onreadystatechange = null;
        this._xhr.onprogress = null;
        this._xhr.onload = null;
        this._xhr.onerror = null;

        this._xhr.abort();

        this._xhr = null;
      }
    }
  }, {
    key: "_onReadyStateChange",
    value: function _onReadyStateChange(e) {
      var xhr = e.target;

      if (xhr.readyState === 2) {
        // HEADERS_RECEIVED
        if (xhr.responseURL != undefined) {
          // if the browser support this property
          var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);

          if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) {
            this._currentRedirectedURL = redirectedURL;

            if (this._onURLRedirect) {
              this._onURLRedirect(redirectedURL);
            }
          }
        }

        if (xhr.status >= 200 && xhr.status <= 299) {
          if (this._waitForTotalLength) {
            return;
          }

          this._status = _loader.LoaderStatus.kBuffering;
        } else {
          this._status = _loader.LoaderStatus.kError;

          if (this._onError) {
            this._onError(_loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, {
              code: xhr.status,
              msg: xhr.statusText
            });
          } else {
            throw new _exception.RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);
          }
        }
      }
    }
  }, {
    key: "_onProgress",
    value: function _onProgress(e) {
      if (this._status === _loader.LoaderStatus.kError) {
        // Ignore error response
        return;
      }

      if (this._contentLength === null) {
        var openNextRange = false;

        if (this._waitForTotalLength) {
          this._waitForTotalLength = false;
          this._totalLengthReceived = true;
          openNextRange = true;
          var total = e.total;

          this._internalAbort();

          if (total != null & total !== 0) {
            this._totalLength = total;
          }
        } // calculate currrent request range's contentLength


        if (this._range.to === -1) {
          this._contentLength = this._totalLength - this._range.from;
        } else {
          // to !== -1
          this._contentLength = this._range.to - this._range.from + 1;
        }

        if (openNextRange) {
          this._openSubRange();

          return;
        }

        if (this._onContentLengthKnown) {
          this._onContentLengthKnown(this._contentLength);
        }
      }

      var delta = e.loaded - this._lastTimeLoaded;
      this._lastTimeLoaded = e.loaded;

      this._speedSampler.addBytes(delta);
    }
  }, {
    key: "_normalizeSpeed",
    value: function _normalizeSpeed(input) {
      var list = this._chunkSizeKBList;
      var last = list.length - 1;
      var mid = 0;
      var lbound = 0;
      var ubound = last;

      if (input < list[0]) {
        return list[0];
      }

      while (lbound <= ubound) {
        mid = lbound + Math.floor((ubound - lbound) / 2);

        if (mid === last || input >= list[mid] && input < list[mid + 1]) {
          return list[mid];
        } else if (list[mid] < input) {
          lbound = mid + 1;
        } else {
          ubound = mid - 1;
        }
      }
    }
  }, {
    key: "_onLoad",
    value: function _onLoad(e) {
      if (this._status === _loader.LoaderStatus.kError) {
        // Ignore error response
        return;
      }

      if (this._waitForTotalLength) {
        this._waitForTotalLength = false;
        return;
      }

      this._lastTimeLoaded = 0;
      var KBps = this._speedSampler.lastSecondKBps;

      if (KBps === 0) {
        this._zeroSpeedChunkCount++;

        if (this._zeroSpeedChunkCount >= 3) {
          // Try get currentKBps after 3 chunks
          KBps = this._speedSampler.currentKBps;
        }
      }

      if (KBps !== 0) {
        var normalized = this._normalizeSpeed(KBps);

        if (this._currentSpeedNormalized !== normalized) {
          this._currentSpeedNormalized = normalized;
          this._currentChunkSizeKB = normalized;
        }
      }

      var chunk = e.target.response;
      var byteStart = this._range.from + this._receivedLength;
      this._receivedLength += chunk.byteLength;
      var reportComplete = false;

      if (this._contentLength != null && this._receivedLength < this._contentLength) {
        // continue load next chunk
        this._openSubRange();
      } else {
        reportComplete = true;
      } // dispatch received chunk


      if (this._onDataArrival) {
        this._onDataArrival(chunk, byteStart, this._receivedLength);
      }

      if (reportComplete) {
        this._status = _loader.LoaderStatus.kComplete;

        if (this._onComplete) {
          this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
        }
      }
    }
  }, {
    key: "_onXhrError",
    value: function _onXhrError(e) {
      this._status = _loader.LoaderStatus.kError;
      var type = 0;
      var info = null;

      if (this._contentLength && this._receivedLength > 0 && this._receivedLength < this._contentLength) {
        type = _loader.LoaderErrors.EARLY_EOF;
        info = {
          code: -1,
          msg: 'RangeLoader meet Early-Eof'
        };
      } else {
        type = _loader.LoaderErrors.EXCEPTION;
        info = {
          code: -1,
          msg: e.constructor.name + ' ' + e.type
        };
      }

      if (this._onError) {
        this._onError(type, info);
      } else {
        throw new _exception.RuntimeException(info.msg);
      }
    }
  }, {
    key: "currentSpeed",
    get: function get() {
      return this._speedSampler.lastSecondKBps;
    }
  }]);
  return RangeLoader;
}(_loader.BaseLoader);

var _default = RangeLoader;
exports.default = _default;